using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Reflection;

namespace Sundstrom.Mvvm
{
    static class ReflectionExtensions
    {
#if NET4
    /// <summary>
    ///   Returns all the fields of a type, working around the fact that reflection
    ///   does not return private fields in any other part of the hierarchy than
    ///   the exact class GetFields() is called on.
    /// </summary>
    /// <param name="type">Type whose fields will be returned</param>
    /// <param name="bindingFlags">Binding flags to use when querying the fields</param>
    /// <returns>All of the type's fields, including its base types</returns>
        public static FieldInfo[] GetFields2(
            this Type type, BindingFlags bindingFlags
        )
        {
            Contract.Requires<ArgumentException>(type != null);

            FieldInfo[] fieldInfos = type.GetFields(bindingFlags);

            // If this class doesn't have a base, don't waste any time
            if (type.BaseType == typeof(object))
            {
                return fieldInfos;
            }
            else
            {   // Otherwise, collect all types up to the furthest base class
                var currentType = type;
                var fieldComparer = new FieldInfoComparer();
                var fieldInfoList = new HashSet<FieldInfo>(fieldInfos, fieldComparer);
                while (currentType != typeof(object))
                {
                    fieldInfos = currentType.GetFields(bindingFlags);
                    fieldInfoList.UnionWith(fieldInfos);
                    currentType = currentType.BaseType;
                }
                return fieldInfoList.ToArray();
            }
        }

        private class FieldInfoComparer : IEqualityComparer<FieldInfo>
        {
            public bool Equals(FieldInfo x, FieldInfo y)
            {
                return x.DeclaringType == y.DeclaringType && x.Name == y.Name;
            }

            public int GetHashCode(FieldInfo obj)
            {
                return obj.Name.GetHashCode() ^ obj.DeclaringType.GetHashCode();
            }
        }

#else

        public static IEnumerable<FieldInfo> GetFields2(
            this Type type
            )
        {
            Contract.Requires<ArgumentException>(type != null);

            while (type != typeof(object))
            {
                var type_ = type.GetTypeInfo();

                foreach (var field in type_.DeclaredFields)
                {
                    yield return field;
                }

                type = type.GetTypeInfo().BaseType;
            }
        }

#endif
    }
}